package com.wangtianxiang.rabbitmq.factory;


import com.wangtianxiang.rabbitmq.config.CusRabbitProperties;
import com.wangtianxiang.rabbitmq.config.FlushRabbitProperties;
import com.wangtianxiang.rabbitmq.utils.SpringBeanUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionNameStrategy;
import org.springframework.amqp.rabbit.connection.RabbitConnectionFactoryBean;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
import org.springframework.boot.autoconfigure.amqp.RabbitRetryTemplateCustomizer;
import org.springframework.boot.context.properties.PropertyMapper;

import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;


@Slf4j
public class RabbitTemplateFactory {

    private static final Lock LOCK = new ReentrantLock();

    private Map<String,RabbitTemplate> rabbitTemplateMap = new ConcurrentHashMap<>(4);

    private Map<String, CusRabbitProperties> cusRabbitPropertiesMap = new ConcurrentHashMap<>(4);

    public RabbitTemplateFactory(Map<String, CusRabbitProperties> cusRabbitPropertiesMap) {
        this.cusRabbitPropertiesMap.putAll(cusRabbitPropertiesMap);
        initRabbitTemplateMap();
    }

    public void initRabbitTemplateMap(){

        this.cusRabbitPropertiesMap.forEach((tenant,cusRabbitProperties) -> {
            try {
                this.createRabbitTemplate(tenant,cusRabbitProperties);
            } catch (Exception e) {
                log.error("tenant:{},createRabbitTemplate error:{}",tenant,e);
            }
        });
    }


    public void createRabbitTemplate(String tenant,CusRabbitProperties cusRabbitProperties) throws Exception {
        rabbitTemplateMap.put(tenant,initRabbitTemplate(cusRabbitProperties));
    }




    public void flushRabbitTemplate(String tenant, FlushRabbitProperties properties){
        LOCK.lock();
        try {
            PropertyMapper map = PropertyMapper.get();
            CusRabbitProperties cusRabbitProperties = cusRabbitPropertiesMap.get(tenant);
            map.from(properties::getConnectionTimeout).whenNonNull()
                    .to(cusRabbitProperties::setConnectionTimeout);
            map.from(properties::getPublisherConfirms).whenNonNull()
                    .to(cusRabbitProperties::setPublisherConfirms);
            map.from(properties::getPublisherReturns).whenNonNull()
                    .to(cusRabbitProperties::setPublisherReturns);

            createRabbitTemplate(tenant,cusRabbitProperties);
        }catch (Exception e){
            log.error("tenant:{},flushRabbitTemplate error:{}",tenant,e);
        }finally {
            LOCK.unlock();
        }
    }

    public RabbitTemplate getRabbitTemplate(String tenant){

        CusRabbitProperties cusRabbitProperties = cusRabbitPropertiesMap.get(tenant);

        RabbitTemplate rabbitTemplate = rabbitTemplateMap.get(tenant);

        if(null != rabbitTemplate){
            return rabbitTemplateMap.get(tenant);
        }
        LOCK.lock();
        try{
            rabbitTemplateMap.put(tenant,initRabbitTemplate(cusRabbitProperties));
            return rabbitTemplateMap.get(tenant);
        }catch (Exception e){
            log.error("getRabbitTemplate error:{}",e);
        }finally {
            LOCK.unlock();
        }

        return null;
    }

    public RabbitTemplate initRabbitTemplate(CusRabbitProperties cusRabbitProperties) throws Exception {
        return rabbitTemplate(cusRabbitProperties, rabbitConnectionFactory(cusRabbitProperties));
    }



    public RabbitTemplate rabbitTemplate(CusRabbitProperties cusRabbitProperties, ConnectionFactory connectionFactory) throws Exception{

        ObjectProvider<MessageConverter> objectProvider = SpringBeanUtils.getBeanProvider(MessageConverter.class);
        ObjectProvider<RabbitRetryTemplateCustomizer> retryTemplateCustomizers = SpringBeanUtils.getBeanProvider(RabbitRetryTemplateCustomizer.class);

        PropertyMapper map = PropertyMapper.get();
        RabbitTemplate template = new RabbitTemplate(connectionFactory);

        if(cusRabbitProperties.isPublisherConfirms()){
            template.setConfirmCallback((RabbitTemplate.ConfirmCallback) Class.forName(cusRabbitProperties.getConfirmCallback()).newInstance());
        }

        if(cusRabbitProperties.isPublisherReturns()){
            template.setReturnCallback((RabbitTemplate.ReturnCallback) Class.forName(cusRabbitProperties.getReturnCallback()).newInstance());
        }

        MessageConverter messageConverter = objectProvider.getIfUnique();
        if (messageConverter != null) {
            template.setMessageConverter(messageConverter);
        }
        template.setMandatory(determineMandatoryFlag(cusRabbitProperties));
        RabbitProperties.Template properties = cusRabbitProperties.getTemplate();
        if (properties.getRetry().isEnabled()) {
            template.setRetryTemplate(new RetryTemplateFactory(
                    retryTemplateCustomizers.orderedStream().collect(Collectors.toList())).createRetryTemplate(
                    properties.getRetry(), RabbitRetryTemplateCustomizer.Target.SENDER));
        }
        map.from(properties::getReceiveTimeout).whenNonNull().as(Duration::toMillis)
                .to(template::setReceiveTimeout);
        map.from(properties::getReplyTimeout).whenNonNull().as(Duration::toMillis).to(template::setReplyTimeout);
        map.from(properties::getExchange).to(template::setExchange);
        map.from(properties::getRoutingKey).to(template::setRoutingKey);
        map.from(properties::getDefaultReceiveQueue).whenNonNull().to(template::setDefaultReceiveQueue);
        return template;
    }


    public boolean determineMandatoryFlag(CusRabbitProperties properties) {
        Boolean mandatory = properties.getTemplate().getMandatory();
        return (mandatory != null) ? mandatory : properties.isPublisherReturns();
    }

    public CachingConnectionFactory rabbitConnectionFactory(CusRabbitProperties properties){

        ObjectProvider<ConnectionNameStrategy> connectionNameStrategy = SpringBeanUtils.getBeanProvider(ConnectionNameStrategy.class);

        PropertyMapper map = PropertyMapper.get();
        CachingConnectionFactory factory = null;
        try {
            factory = new CachingConnectionFactory(
                    getRabbitConnectionFactoryBean(properties).getObject());
        } catch (Exception e) {
            e.printStackTrace();
        }
        map.from(properties::determineAddresses).to(factory::setAddresses);
        map.from(properties::isPublisherConfirms).to(factory::setPublisherConfirms);
        map.from(properties::isPublisherReturns).to(factory::setPublisherReturns);
        RabbitProperties.Cache.Channel channel = properties.getCache().getChannel();
        map.from(channel::getSize).whenNonNull().to(factory::setChannelCacheSize);
        map.from(channel::getCheckoutTimeout).whenNonNull().as(Duration::toMillis)
                .to(factory::setChannelCheckoutTimeout);
        RabbitProperties.Cache.Connection connection = properties.getCache().getConnection();
        map.from(connection::getMode).whenNonNull().to(factory::setCacheMode);
        map.from(connection::getSize).whenNonNull().to(factory::setConnectionCacheSize);
        map.from(connectionNameStrategy::getIfUnique).whenNonNull().to(factory::setConnectionNameStrategy);
        return factory;
    }

    public RabbitConnectionFactoryBean getRabbitConnectionFactoryBean(RabbitProperties properties)
            throws Exception {
        PropertyMapper map = PropertyMapper.get();
        RabbitConnectionFactoryBean factory = new RabbitConnectionFactoryBean();
        map.from(properties::determineHost).whenNonNull().to(factory::setHost);
        map.from(properties::determinePort).to(factory::setPort);
        map.from(properties::determineUsername).whenNonNull().to(factory::setUsername);
        map.from(properties::determinePassword).whenNonNull().to(factory::setPassword);
        map.from(properties::determineVirtualHost).whenNonNull().to(factory::setVirtualHost);
        map.from(properties::getRequestedHeartbeat).whenNonNull().asInt(Duration::getSeconds)
                .to(factory::setRequestedHeartbeat);
        RabbitProperties.Ssl ssl = properties.getSsl();
        if (ssl.isEnabled()) {
            factory.setUseSSL(true);
            map.from(ssl::getAlgorithm).whenNonNull().to(factory::setSslAlgorithm);
            map.from(ssl::getKeyStoreType).to(factory::setKeyStoreType);
            map.from(ssl::getKeyStore).to(factory::setKeyStore);
            map.from(ssl::getKeyStorePassword).to(factory::setKeyStorePassphrase);
            map.from(ssl::getTrustStoreType).to(factory::setTrustStoreType);
            map.from(ssl::getTrustStore).to(factory::setTrustStore);
            map.from(ssl::getTrustStorePassword).to(factory::setTrustStorePassphrase);
            map.from(ssl::isValidateServerCertificate)
                    .to((validate) -> factory.setSkipServerCertificateValidation(!validate));
            map.from(ssl::getVerifyHostname).to(factory::setEnableHostnameVerification);
        }
        map.from(properties::getConnectionTimeout).whenNonNull().asInt(Duration::toMillis)
                .to(factory::setConnectionTimeout);
        factory.afterPropertiesSet();
        return factory;
    }

}



